package com.zkh.myframe.database;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.zkh.myframe.annotation.Table;
import com.zkh.myframe.database.EntityUtils.EntityField;
import com.zkh.myframe.enumeration.annotation.KeyType;
import com.zkh.myutils.database.DatabaseException;
import com.zkh.myutils.database.dbpool.DBType;
import com.zkh.myutils.database.enums.Symbol;
import com.zkh.myutils.utils.ArrayUtils;
import com.zkh.myutils.utils.Assert;
import com.zkh.myutils.utils.StringUtils;
import com.zkh.myutils.utils.Utils;

/**
 * 基础Dao，继承该对象的Dao可直接调用变量dataTemplate(即DataTemplate对象)。
 * @author zkh
 *
 */
public class BaseDao {
	
	/**
	 * 数据库操作模板对象
	 */
	private final static ThreadLocal<DataTemplate> theadTemplate = new ThreadLocal<>();
	
	/**
	 * 获取模板
	 * @return
	 */
	public DataTemplate getTemplate() {
		//获取对象
		DataTemplate dateTemplate = theadTemplate.get();
		//为空，创建
		if(dateTemplate==null) {
			//创建并设置
			theadTemplate.set(dateTemplate = new DataTemplate(()->release()));
		}
		//返回
		return dateTemplate;
	}
	
	/**
	 * 设置数据源。非必须，不设置则使用默认数据源
	 * @param datasourceKey 数据源唯一标识key
	 */
	public DataTemplate setDataSource(String datasourceKey) {
		//设置数据源
		return getTemplate().setDataSource(datasourceKey);
	}
	
	/**
	 * 设置数据源。非必须，不设置则使用默认数据源
	 * @param datasourceKey 数据源唯一标识key
	 */
	public BaseDao setDataSourceReturnThis(String datasourceKey) {
		//设置数据源
		setDataSource(datasourceKey);
		//返回当前对象
		return this;
	}
	
	/**
	 * 设置表前缀，用以访问其它数据库或用户库
	 * @param tablePrefix 表前缀
	 */
	public DataTemplate setTablePrefix(String tablePrefix) {
		//设置数据源
		return getTemplate().setTablePrefix(tablePrefix);
	}
	
	/**
	 * 设置表前缀，用以访问其它数据库或用户库
	 * @param tablePrefix 表前缀
	 */
	public BaseDao setTablePrefixReturnThis(String tablePrefix) {
		//设置数据源
		setTablePrefix(tablePrefix);
		//返回当前对象
		return this;
	}
	
	/**
	 * 全表查询
	 * @param cls 实体对象class
	 * @return
	 */
	public <T> List<T> doSelect(Class<T> clazz){
		//数据操作对象
		DataTemplate dataTemplate = getTemplate();
		//添加表名
		dataTemplate.addTable(clazz.getAnnotation(Table.class).name()+" t");
		//添加表字段
		dataTemplate.addColumn("t.*");
		//执行查询
		return dataTemplate.doSelect(clazz);
	}
	
	/**
	 * 根据配置的实体主键查询实体对象
	 * @param cls 实体对象class
	 * @param keyvalues 主键值。如果是联合主键，按Table注解的主键配置顺序依次赋值
	 * @return
	 */
	public <T> T doSelectByKey(Class<T> cls, Object... keyvalues) {
		//数据操作对象
		DataTemplate dataTemplate = getTemplate();
		//为空
		if(keyvalues==null || keyvalues.length==0) {
			throw new DatabaseException("主键值不能为空");
		}
		//获取注解
		Table table = cls.getAnnotation(Table.class);
		//表名
		dataTemplate.addTable(table.name() + " t");
		//查询列
		dataTemplate.addColumn("t.*");
		//主键
		String[] keys = table.keys();
		//赋值
		for(int i=0,len=keys.length; i<len; i++) {
			//条件
			dataTemplate.addCondition(keys[i], Symbol.EQ, keyvalues[i]);
		}
		//查询
		return dataTemplate.doSelectOne(cls);
	}
	
	/**
	 * 添加实体，返回受影响行数
	 * @param entity 实体对象
	 * @return
	 */
	public <T> int doAdd(T entity){
		//处理参数
		formatParam(Arrays.asList(entity), 1);
		//执行新增
		return getTemplate().doAdd();
	}
	
	/**
	 * 批量添加实体，返回受影响行数
	 * @param entityList 实体集合
	 */
	public <T> int[] doAddBatch(List<T> entityList){
		return Assert.getIfElse(entityList==null || entityList.size()==0, ()->new int[] {0}, ()->getTemplate().addBatchParams(formatParam(entityList, 1)).doBatchAdd());
	}
	
	/**
	 * 添加实体，返回自增ID，如果没有，则返回null
	 * @param entity 实体对象
	 * @return
	 */
	public <T> BigDecimal doInsert(T entity){
		//处理参数
		formatParam(Arrays.asList(entity), 1);
		//执行新增
		return getTemplate().doInsert();
	}
	
	/**
	 * 批量添加实体
	 * @param entityList 实体集合
	 */
	public <T> BigDecimal[] doInsertBatch(List<T> entityList){
		return Assert.getIfElse(entityList==null || entityList.size()==0, ()->new BigDecimal[] {BigDecimal.ZERO}, ()->getTemplate().addBatchParams(formatParam(entityList, 1)).doBatchInsert());
	}
	
	/**
	 * 更新实体
	 * @param entity 实体
	 * @return
	 */
	public <T> int doUpdate(T entity){
		//处理参数
		formatParam(Arrays.asList(entity), 2);
		//执行更新
		return getTemplate().doUpdate();
	}
	
	/**
	 * 批量更新实体
	 * @param entityList 实体集合
	 * @return
	 */
	public <T> int[] doUpdateBatch(List<T> entityList){
		return Assert.getIfElse(entityList==null || entityList.size()==0, ()->new int[] {0}, ()->getTemplate().addBatchParams(formatParam(entityList, 2)).doBatchUpdate());
	}
	
	/**
	 * 新增或更新，根据主键值判断是更新还是新增<br>
	 * <b>新增规则：单主键，且主键值为空</b>
	 * @param entity 实体
	 * @return 如果是新增，主键自增返回自增值，主键UUID返回null；如果是更新操作，返回受影响行数
	 */
	public <T> BigDecimal doAddOrUpdate(T entity){
		//数据操作对象
		DataTemplate dataTemplate = getTemplate();
		Class<?> encls = entity.getClass();
		//注解
		Table table = encls.getAnnotation(Table.class);
		//表名
		dataTemplate.addTable(table.name());
		//多主键，更新标识
		boolean multiPk = table.keys().length>1;boolean[] update = {false};
		//获取值
		List<EntityField> colsList = EntityUtils.getFieldValue(dataTemplate.ds, Arrays.asList(entity), encls, table, dataTemplate.getTablePrefix()).get(0);
		//遍历值
		colsList.forEach(ef->{
			//主键
			if(ef.isPrimaryKey()) {
				//多主键或单主键值不为空，更新操作
				if(multiPk || ef.getFieldValue()!=null) {
					//多主键，值不能为空
					if(ef.getFieldValue()==null) {
						throw new DatabaseException("多主键，默认更新操作，主键值不能为空");
					}
					dataTemplate.addCondition(ef.getFieldName(), Symbol.EQ, ef.getFieldValue());
					update[0] = true;
				}else {	//单主键，值为空，新增操作
					//值为UUID
					if(table.keyvalue()==KeyType.UUID) {
						dataTemplate.addOperColumn(ef.getFieldName(), Utils.getUUID());
					}//值自增且为Oracle，取序列
					else if(DBType.TYPE_ORACLE.equals(dataTemplate.getDbType())) {
						if(StringUtils.isEmpty(table.sequence())) {
							throw new DatabaseException("请设置序列");
						}
						dataTemplate.addOperColumn(ef.getFieldName(), table.sequence() + ".nextval()", false);
					}
				}
			}else {
				//不是主键，普通操作字段
				dataTemplate.addOperColumn(ef.getFieldName(), ef.getFieldValue());
			}
		});
		//执行
		return update[0] ? BigDecimal.valueOf(dataTemplate.doUpdate()) : dataTemplate.doInsert();
	}
	
	/**
	 * 批量新增或更新，根据主键值判断是更新还是新增<br>
	 * 新增规则：单主键，且主键值为空
	 * @param entityList
	 * @return
	 */
	public <T> void doAddOrUpdateBatch(List<T> entityList) {
		//有参数
		if(entityList!=null && entityList.size()>0) {
			//数据操作对象
			DataTemplate dataTemplate = getTemplate();
			//获取第一个实体，用来获取基本信息
			Class<?> encls = entityList.get(0).getClass();
			//注解
			Table table = encls.getAnnotation(Table.class);
			//复合主键
			boolean multiPk = table.keys().length>1;
			//新增容器和更新容器
			List<List<EntityField>> insertList = new ArrayList<>();
			List<List<EntityField>> updateList = new ArrayList<>();
			//获取批量值
			List<List<EntityField>> list = EntityUtils.getFieldValue(dataTemplate.ds, entityList, encls, table, dataTemplate.getTablePrefix());
			List<EntityField> firstRow = list.get(0); int cols = firstRow.size();
			//主键索引
			int pkIdx = -1;
			for(int i=0; i<cols; i++) {
				if(firstRow.get(i).isPrimaryKey()) {
					pkIdx = i;break;
				}
			}
			//没有主键或多主键，默认更新
			if(pkIdx==-1 || multiPk) {
				updateList = list;
			}else {
				for(List<EntityField> e: list) {
					if(e.get(pkIdx).getFieldValue()==null) {
						insertList.add(e);
					}else {
						updateList.add(e);
					}
				}
			}
			//参数
			List<Object[]> paramList = new ArrayList<>();
			/** 分别处理更新和新增操作 **/
			//更新
			if(updateList.size()>0) {
				//首行，用来生成SQL
				firstRow = updateList.get(0);
				//表名
				dataTemplate.addTable(table.name());
				//遍历字段
				firstRow.forEach(col->{
					if(col.isPrimaryKey()) {
						dataTemplate.addCondition(col.getFieldName(), Symbol.EQ, col.getFieldValue());
					}else if(col.isUpdate()){
						dataTemplate.addOperColumn(col.getFieldName());
					}
				});
				//遍历行
				for(List<EntityField> tmpList: updateList) {
					//行参数
					List<Object> tmpParamList = new ArrayList<Object>();
					//遍历列
					tmpList.forEach(col->{
						//更新或主键
						if(col.isPrimaryKey() || col.isUpdate()) {
							tmpParamList.add(col.getFieldValue());
						}
					});
					paramList.add(tmpParamList.toArray());
				}
				//执行
				dataTemplate.addBatchParams(paramList).doBatchUpdate();
			}
			//新增
			if(insertList.size()>0) {
				//oracle
				boolean oracle = DBType.TYPE_ORACLE.equals(dataTemplate.getDbType());
				//清空参数
				paramList.clear();
				//表名
				dataTemplate.addTable(table.name());
				//首行，用来生成SQL
				firstRow = insertList.get(0);
				//遍历字段
				firstRow.forEach(col->{
					if(col.isPrimaryKey()) {
						//自增
						if(table.keyvalue()==KeyType.AUTO) {
							//Oracle新增序列，其它数据库不指定列名
							if(oracle) {
								dataTemplate.addOperColumn(col.getFieldName(), table.sequence() + ".nextval", false);
							}
						}else {//手动或UUID
							dataTemplate.addOperColumn(col.getFieldName());
						}
					}else if(col.isInsert()){
						dataTemplate.addOperColumn(col.getFieldName());
					}
				});
				//遍历行
				for(List<EntityField> tmpList: insertList) {
					//行参数
					List<Object> tmpParamList = new ArrayList<Object>();
					//遍历列
					tmpList.forEach(col->{
						if(col.isPrimaryKey()) {
							if(table.keyvalue()==KeyType.MANUAL) {
								tmpParamList.add(col.getFieldValue());
							}else if(table.keyvalue()==KeyType.UUID) {
								tmpParamList.add(Utils.getUUID());
							}
						}else if(col.isInsert()){
							tmpParamList.add(col.getFieldValue());
						}
					});
					paramList.add(tmpParamList.toArray());
				}
				//执行
				dataTemplate.addBatchParams(paramList).doBatchInsert();
			}
		}
	}
	
	/**
	 * 删除对象（根据主键）
	 * @param entity 实体
	 * @return
	 */
	public <T> int doDelete(T entity){
		return doDeleteBatch(Arrays.asList(entity));
	}
	
	/**
	 * 批量删除对象（根据主键）
	 * @param entityList 实体集合
	 * @return
	 */
	public <T> int doDeleteBatch(List<T> entityList){
		if(entityList==null || entityList.size()==0) {
			return 0;
		}
		//数据操作对象
		DataTemplate dataTemplate = getTemplate();
		//Class
		Class<?> cls = entityList.get(0).getClass();
		//Table注解
		Table table = cls.getAnnotation(Table.class);
		//添加表
		dataTemplate.addTable(table.name());
		//主键
		String[] keys = table.keys();
		try {
			//getter
			List<Method> methodList = new ArrayList<Method>();
			//遍历主键并获取GETTER
			for(String k: keys) {
				methodList.add(cls.getMethod("get" + StringUtils.uppercaseFirst(k)));
				//批量操作条件。批量操作不用传值，后续直接传递批量参数
				Assert.doIfTrue(entityList.size()>1, ()->dataTemplate.addCondition(k, Symbol.EQ, null));
			}
			//批处理参数
			List<Object[]> paramList = new ArrayList<Object[]>();
			//遍历实体
			for(T t: entityList) {
				//主键值容器
				Object[] params = new Object[keys.length];
				//遍历主键
				for(int i=0,len=keys.length; i<len; i++) {
					params[i] = methodList.get(i).invoke(t);
				}
				//保存参数
				paramList.add(params);
			}
			//单条记录
			if(paramList.size()==1) {
				//参数
				Object[] ps = paramList.get(0);
				//添加条件
				ArrayUtils.forEach(keys, (key, i)->dataTemplate.addCondition(key, Symbol.EQ, ps[i]));
				//执行删除
				return dataTemplate.doDelete();
			}//批量
			else {
				//执行批处理
				int[] tmpRes = dataTemplate.addBatchParams(paramList).doBatchDelete();
				//结果
				int res = 0; for(int t: tmpRes) res += t;
				//返回结果
				return res;
			}
		}catch(NoSuchMethodException e) {
			throw new DatabaseException("存在主键没有对应的getter");
		} catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			throw new DatabaseException("执行主键getter时发生错误");
		}
	}
	
	/**
	 * 批量添加操作列，键和值必须一一对应
	 * @param keys 键集合
	 * @param values 值集合
	 */
	public DataTemplate addOperColumn(List<String> keys, List<Object> values) {
		//数据操作对象
		DataTemplate dataTemplate = getTemplate();
		//遍历
		for(int i=0,len=Math.min(keys.size(), values.size()); i<len; i++) {
			dataTemplate.addOperColumn(keys.get(i), values.get(i));
		}
		return dataTemplate;
	}
	
	/**
	 * 批量添加条件，比较符为等于，逻辑连接符为and
	 * @param kvs 参数，格式为：key1, value1, key2, value2...依次类推，其中key必须为String类型
	 */
	public DataTemplate addConditionEQ(Object... kvs) {
		//数据操作对象
		DataTemplate dataTemplate = getTemplate();
		//遍历
		for(int i=1,len=kvs.length; i<len; i+=2) {
			dataTemplate.addCondition((String) kvs[i-1], kvs[i]);
		}
		return dataTemplate;
	}

	/**
	 * 格式化批量参数
	 * @param entityList 待新增或更新的实体
	 * @param type 操作类型。1：新增，2：更新
	 * @return
	 */
	private <T> Object[][] formatParam(List<T> entityList, int type) {
		//数据操作对象
		DataTemplate dataTemplate = getTemplate();
		//获取第一个实体，用来获取基本信息
		Class<?> encls = entityList.get(0).getClass();
		//注解
		Table table = encls.getAnnotation(Table.class);
		boolean oracle = DBType.TYPE_ORACLE.equals(dataTemplate.getDbType());
		//表名
		dataTemplate.addTable(table.name());
		//获取批量值
		List<List<EntityField>> list = EntityUtils.getFieldValue(dataTemplate.ds, entityList, encls, table, dataTemplate.getTablePrefix());
		//取第一行数据，用来生成SQL
		list.get(0).forEach(ef->{
			//主键，忽略参数失效
			if(ef.isPrimaryKey()) {
				//更新
				if(type==2) {
					dataTemplate.addCondition(ef.getFieldName(), Symbol.EQ, ef.getFieldValue());
				}//新增
				else if(type==1) {
					//自增
					if(table.keyvalue()==KeyType.AUTO) {
						//Oracle新增序列，其它数据库不指定列名
						if(oracle) {
							dataTemplate.addOperColumn(ef.getFieldName(), table.sequence() + ".nextval", false);
						}
					}else {//手动或UUID
						dataTemplate.addOperColumn(ef.getFieldName(), ef.getFieldValue());
					}
				}
			}//非主键
			else if((type==1 && ef.isInsert()) || (type==2 && ef.isUpdate())){
				dataTemplate.addOperColumn(ef.getFieldName(), ef.getFieldValue());
			}
		});
		//最终值
		Object[][] paramss = new Object[list.size()][];
		//遍历
		for(int i=0,len=list.size(); i<len; i++) {
			//行容器
			List<Object> rowList = new ArrayList<Object>();
			//获取行数据并遍历
			for(EntityField ef: list.get(i)) {
				//主键，忽略参数失效
				if(ef.isPrimaryKey()) {
					//更新
					if(type==2) {
						rowList.add(ef.getFieldValue());
					}//新增
					else if(type==1) {
						//手动
						if(table.keyvalue()==KeyType.MANUAL) {
							rowList.add(ef.getFieldValue());
						}
						//UUID
						else if(table.keyvalue()==KeyType.UUID) {
							rowList.add(Utils.getUUID());
						}
					}
				}//非主键
				else if((type==1 && ef.isInsert()) || (type==2 && ef.isUpdate())){
					rowList.add(ef.getFieldValue());
				}
			}
			//保存
			paramss[i] = rowList.toArray();
		}
		return paramss;
	}
	
	/**
	 * 释放ThreadLocal资源
	 */
	private void release() {
		//存在，则移除
		if(theadTemplate.get()!=null) {
			theadTemplate.remove();
		}
	}
}
